package org.meichao.config.aop;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.meichao.config.annotation.Sign;
import org.meichao.utils.RespInfoUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

/**
 *  标签aop
 */
@Aspect
@Component
public class SignAop {

    private static final Logger logger = LoggerFactory.getLogger(SignAop.class);

    /**
     * 验签
     *  annotation(xxx)中 xxx需与方法参数中 sign 一致
     * @param pjp
     * @param sign
     * @return
     */
    @Around("@annotation(sign)")
    public Object doBefore(ProceedingJoinPoint pjp, Sign sign){
        //默认返回
        Object obj = RespInfoUtils.create().respFailure("签名验证失败").toRespInfo();
        //获取请求参数
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //前端所传token
        String token = request.getHeader("token");
        //前端所传标签
        String webSign = request.getHeader("sign");
        //请求携带的参数
        Object[] args = pjp.getArgs();
        //存放请求中需要验签的参数
        JSONObject verifyJson = new JSONObject();
        //获取参数
        try {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            //String[] paramNames = signature.getParameterNames();
            Parameter[] parameters = signature.getMethod().getParameters();
            for(int i = 0; i < parameters.length; i++){
                Parameter param = parameters[i];
                Class<?> paramType = param.getType();
                //从请求体中获取,目前只验证post请求
                RequestBody requestBody = param.getAnnotation(RequestBody.class);
                if(!Objects.isNull(requestBody)){
                    JSONObject arg;
                    if(Objects.equals(paramType,JSONObject.class)){
                        arg = (JSONObject) args[i];
                    }else{
                        //将自定义的数据类型转换为JSONObject
                        arg = transferJson(args[i]);
                    }
                    //把参数加入检验json中
                    verifyJson.putAll(arg);
                }
            }
            //token加入检验json中
            verifyJson.put("token", token);
            logger.info("验签参数：{}", verifyJson);
            //生成标签 与前端传的标签进行比较
            String serverSign = generateSign(verifyJson, "boot.sign.secret.key");
            logger.info("******web端签名******:{}", webSign);
            logger.info("******server端签名******:{}",serverSign);
            if(Objects.equals(webSign,serverSign)){
                //.proceed()目标方法执行
                Object resp = pjp.proceed();
                logger.info("目标方法返回:{}", resp);
                obj = resp;
            }

        } catch (IllegalAccessException e) {
            logger.error("自定义数据转JSON失败");
            e.printStackTrace();
        } catch (Throwable throwable) {
            logger.error("目标方法执行异常");
            throwable.printStackTrace();
        }
        return obj;
    }

    /**
     * 将自定义的数据类型转换为JSONObject
     * @param t
     * @param <T>
     * @return
     */
    public static <T> JSONObject transferJson(T t) throws IllegalAccessException {
        Class<?> clazz = t.getClass();
        //获取所有声明的字段
        Field[] fields = clazz.getDeclaredFields();
        JSONObject json = new JSONObject();
        for (Field field : fields) {
            //反射无法操作私有变量，这里将访问权限设置为true
            field.setAccessible(true);
            //获取字段的名称
            String name = field.getName();
            Object obj = field.get(t);
            json.put(name, obj);
        }
        return json;
     }

    /**
     * 生成标签
     *  请求中的参数 + 约定的密钥生成标签
     * @param param
     * @param secretKey
     * @return
     */
     public static String generateSign(JSONObject param, String secretKey){
         //1.将param里的参数，按key字典排序
         Set<String> keys = param.keySet();
         //treeSet有序的set集合
         TreeSet<String> keySet = new TreeSet<>(keys);
         //2.将param的参数拼装成k,v&k,&...形式
         StringBuilder sb = new StringBuilder();
         for(String k : keySet){
             String v = param.getString(k);
             if(StringUtils.isBlank(v)){
                 continue;
             }
             sb.append(String.format("%s=%s%s",k,v,"&"));
         }
         //末尾添加密钥
         sb.append(String.format("%s=%s","secret",secretKey));
         logger.info("生成的标签串:{}", sb);
         //3.将得到的字符串加密,先sha25 再md5
         return DigestUtils.md5Hex(DigestUtils.sha256Hex(sb.toString()));
     }
}
